﻿#pragma once

#include <QListWidget>
#include <QPlainTextEdit>
#include <QToolBar>
#include <QComboBox>
#include "AbstractEditorWidget.h"

class QLabel;

namespace Domy {

class LineNumberWidget;
class XMLSyntaxHighlighter;
class TextEditor;
class EditorBottomBar;

/**
 * @brief The TextEditorWidget class
 * @note  文本编辑器窗口
 */
class TextEditorWidget : public AbstractEditorWidget
{
    Q_OBJECT
public:
    explicit TextEditorWidget(QWidget *parent = Q_NULLPTR);
    ~TextEditorWidget();

    /**
     * @brief getEditor
     * @note  获取编辑器
     */
    TextEditor *getEditor();

    /**
     * @brief triggerAction
     * @note  触发动作
     * @param key 动作key
     */
    virtual void triggerAction(ActionKey key) override;

signals:
    /**
     * @brief signalReload
     * @note  重新加载信号
     */
    void signalReload();

protected:
    virtual void keyPressEvent(QKeyEvent *event) override;

private slots:
    void slotOnFind(const QString &text, bool is_case_sensitive, bool is_whole_word);
    void slotOnFindNext(const QString &text, bool is_case_sensitive, bool is_whole_word);
    void slotOnFindPrevious(const QString &text, bool is_case_sensitive, bool is_whole_word);
    void slotOnClearFindResult();
    void slotOnReplace(const QString &before, const QString &after, bool is_case_sensitive, bool is_whole_word);
    void slotOnCodecNameChanged(const QString &name);
    void slotOnContentModificationChanged(bool is_changed);
    void slotOnCursorPositionChanged();

private:
    TextEditor       *m_editor;
    EditorBottomBar  *m_bar;
};

/**
 * @brief The TextEditor class
 * @note  文本编辑器
 */
class TextEditor : public QPlainTextEdit
{
    Q_OBJECT
    friend class LineNumberWidget;
public:
    explicit TextEditor(QWidget *parent = Q_NULLPTR);
    ~TextEditor();

    enum EncaseType {
        Encase_Unknown = 0,
        Encase_Comment = 0,
        Encase_Apostrophe,
        Encase_Quotation,
        Encase_AngleBracket,
        Encase_Paren,
        Encase_Bracket,
        Encase_Brace
    };

    /**
     * @brief setErrorLineHighlight
     * @note  高亮显示错误行
     */
    void setErrorLineHighlight(int line);

    /**
     * @brief clearErrorLineHighlight
     * @note  清除错误行高亮
     */
    void clearErrorLineHighlight();

    /**
     * @brief updateSettings
     * @note  更新设置
     */
    void updateSettings();

    /**
     * @brief getCodecName
     * @note  获取编码名称
     */
    QString getCodecName() const;

    /**
     * @brief setCodecName
     * @note  设置编码名称
     */
    void setCodecName(const QString &codec_name);

    /**
     * @brief findText
     * @note  查找文本
     * @param text              文本
     * @param is_case_sensitive 大小写敏感
     * @param is_whole_word     整词
     */
    void findText(const QString &text, bool is_case_sensitive, bool is_whole_word);

    /**
     * @brief findNext
     * @note  查找下一个
     * @param text              查找文本
     * @param is_case_sensitive 大小写敏感
     * @param is_whole_word     整词
     */
    bool findNext(const QString &text, bool is_case_sensitive, bool is_whole_word);

    /**
     * @brief findPrevious
     * @note  查找前一个
     * @param text              查找文本
     * @param is_case_sensitive 大小写敏感
     * @param is_whole_word     整词
     */
    bool findPrevious(const QString &text, bool is_case_sensitive, bool is_whole_word);

    /**
     * @brief clearFindResult
     * @note  清除查找结果
     */
    void clearFindResult();

    /**
     * @brief replace
     * @note  替换文本
     * @param before            替换前文本
     * @param after             替换后文本
     * @param is_case_sensitive 大小写敏感
     * @param is_whole_word     整词替换
     */
    void replace(const QString &before, const QString &after, bool is_case_sensitive, bool is_whole_word);

    /**
     * @brief getSelectedText
     * @note  获取选中的文本
     */
    QString getSelectedText() const;

    /**
     * @brief encaseSelectedText
     * @note  将选中文本包含
     * @param encase 包含类型
     */
    void encaseSelectedText(EncaseType encase);

protected:
    /**
     * @brief resizeEvent
     * @note  窗口尺寸改变事件
     * @param event
     */
    virtual void resizeEvent(QResizeEvent *event) override;

    virtual void keyPressEvent(QKeyEvent *event) override;

private slots:
    /**
     * @brief slotOnBlockCountChanged
     * @note  响应文本块数量改变
     */
    void slotOnBlockCountChanged(int);

    /**
     * @brief slotOnUpdateRequest
     * @note  响应刷新请求
     * @param rect 刷新区域
     * @param dy   滚动y值
     */
    void slotOnUpdateRequest(const QRect &rect, int dy);

    /**
     * @brief slotOnCursorPositionChanged
     * @note  响应光标位置改变
     */
    void slotOnCursorPositionChanged();

    /**
     * @brief slotOnCustomContextMenuRequest
     * @note  右键菜单
     * @param pos 位置
     */
    void slotOnCustomContextMenuRequest(const QPoint &pos);

private:
    /**
     * @brief highlightCurrentLine
     * @note  高亮显示当前行
     */
    void highlightCurrentLine();

    /**
     * @brief getLineNumberWidgetWidth
     * @note  获取行号窗口宽度
     */
    int getLineNumberWidgetWidth();

    /**
     * @brief getFindFlags
     * @note  获取查找标识
     * @param is_backward       反向查找
     * @param is_case_sensitive 大小写敏感
     * @param is_whole_word     整词
     */
    QTextDocument::FindFlags getFindFlags(bool is_backward, bool is_case_sensitive, bool is_whole_word);

private:
    LineNumberWidget     *m_num_widget;
    int                   m_err_line;

    QList<QTextEdit::
    ExtraSelection>       m_extra_selection;

    XMLSyntaxHighlighter *m_highlighter;

    int                   m_tab_size;
    TabPolicy             m_tab_policy;

    QString               m_codec_name;
};

/**
 * @brief The LineNumberWidget class
 * @note  文本编辑器行号显示窗口
 */
class LineNumberWidget : public QWidget
{
    Q_OBJECT
public:
    explicit LineNumberWidget(TextEditor *edit_widget);
    ~LineNumberWidget() {}

    void setBackgroundColor(const QColor &color);

    void setTextColor(const QColor &color);

protected:
    virtual void paintEvent(QPaintEvent *event) override;

private:
    TextEditor       *m_edit_widget;

    QColor            m_bg_color;
    QColor            m_text_color;
};

/**
 * @brief The CodecNameComboBox class
 * @note  编码名称下拉框控件
 */
class CodecNameComboBox : public QComboBox
{
    Q_OBJECT
public:
    explicit CodecNameComboBox(QWidget *parent = Q_NULLPTR);
    ~CodecNameComboBox();

protected:
    virtual void wheelEvent(QWheelEvent *event) override;
};

/**
 * @brief The EditorBottomBar class
 * @note  编辑器底栏
 */
class EditorBottomBar : public QToolBar
{
    Q_OBJECT
public:
    explicit EditorBottomBar(QWidget *parent = Q_NULLPTR);
    ~EditorBottomBar();

    /**
     * @brief setCodecName
     * @note  设置编码名称
     * @param name
     */
    void setCodecName(const QString &name);

    /**
     * @brief setInfo
     * @note  设置信息
     * @param length 长度
     * @param rows   行数
     */
    void setInfo(int length, int lines, int row, int column);

    /**
     * @brief setCodecNameEditable
     * @note  设置编码名称是否可编辑
     * @param is_editable 编码名称编辑状态
     */
    void setCodecNameEditable(bool is_editable);

    /**
     * @brief isCaseSensitive
     * @note  获取大小写敏感状态
     */
    bool isCaseSensitive() const;

    /**
     * @brief isWholeWord
     * @note  获取整词状态
     */
    bool isWholeWord() const;

    /**
     * @brief setFindText
     * @note  设置查找文本
     * @param text 查找文本
     */
    void setFindText(const QString &text);

    /**
     * @brief getFindText
     * @note  获取查找文本
     */
    QString getFindText() const;

    /**
     * @brief getReplaceText
     * @note  获取替换文本
     */
    QString getReplaceText() const;

signals:
    /**
     * @brief signalCodecNameChanged
     * @note  编码名称改变信号
     * @param codec_name 编码名称
     */
    void signalCodecNameChanged(const QString &codec_name);

    /**
     * @brief signalFind
     * @note  查找信号
     * @param text              查找文本
     * @param is_case_sensitive 大小写敏感
     * @param is_whole_word     整词查找
     */
    void signalFind(const QString &text, bool is_case_sensitive, bool is_whole_word);

    /**
     * @brief signalFindNext
     * @note  查找下一个信号
     * @param text              查找文本
     * @param is_case_sensitive 大小写敏感
     * @param is_whole_word     整词查找
     */
    void signalFindNext(const QString &text, bool is_case_sensitive, bool is_whole_word);

    /**
     * @brief signalFindPrevious
     * @note  查找上一个信号
     * @param text              查找文本
     * @param is_case_sensitive 大小写敏感
     * @param is_whole_word     整词查找
     */
    void signalFindPrevious(const QString &text, bool is_case_sensitive, bool is_whole_word);

    /**
     * @brief signalClearFindResult
     * @note  清除查找结果
     */
    void signalClearFindResult();

    /**
     * @brief signalReplace
     * @note  替换
     * @param before            替换前文本
     * @param after             替换后文本
     * @param is_case_sensitive 大小写敏感
     * @param is_whole_word     整词替换
     */
    void signalReplace(const QString &before, const QString &after,
                       bool is_case_sensitive, bool is_whole_word);

private slots:
    void slotOnActionTriggered(QAction *action);
    void slotOnFindTextChanged(const QString &text);
    void slotOnFindTextEditFinished();
    void slotOnReplaceTextEditFinished();

private:
    QString            m_find_text;

    QLabel            *m_label_length;
    QLabel            *m_label_lines;
    QLabel            *m_label_row;
    QLabel            *m_label_column;
    QLineEdit         *m_le_find;
    QLineEdit         *m_le_replace;
    CodecNameComboBox *m_combo_codec;

    bool               m_is_case_sensitive;
    bool               m_is_whole_word;
};

} // namespace Domy
